<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Allows customization of Messages on-the-fly.
 *
 * @package    Swift
 * @subpackage Plugins
 * @author     Chris Corbyn
 * @author     Fabien Potencier
 */
class Swift_Plugins_DecoratorPlugin implements Swift_Events_SendListener, Swift_Plugins_Decorator_Replacements
{
	/** The replacement map */
	private $_replacements;

	/** The body as it was before replacements */
	private $_originalBody;

	/** The original headers of the message, before replacements */
	private $_originalHeaders = array();

	/** Bodies of children before they are replaced */
	private $_originalChildBodies = array();

	/** The Message that was last replaced */
	private $_lastMessage;

	/**
	 * Create a new DecoratorPlugin with $replacements.
	 *
	 * The $replacements can either be an associative array, or an implementation
	 * of {@link Swift_Plugins_Decorator_Replacements}.
	 *
	 * When using an array, it should be of the form:
	 * <code>
	 * $replacements = array(
	 *  "address1@domain.tld" => array("{a}" => "b", "{c}" => "d"),
	 *  "address2@domain.tld" => array("{a}" => "x", "{c}" => "y")
	 * )
	 * </code>
	 *
	 * When using an instance of {@link Swift_Plugins_Decorator_Replacements},
	 * the object should return just the array of replacements for the address
	 * given to {@link Swift_Plugins_Decorator_Replacements::getReplacementsFor()}.
	 *
	 * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements
	 */
	public function __construct($replacements)
	{
		$this->setReplacements($replacements);
	}

	/**
	 * Sets replacements.
	 *
	 * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements
	 *
	 * @see __construct()
	 */
	public function setReplacements($replacements)
	{
		if (!($replacements instanceof \Swift_Plugins_Decorator_Replacements)) {
			$this->_replacements = (array)$replacements;
		} else {
			$this->_replacements = $replacements;
		}
	}

	/**
	 * Invoked immediately before the Message is sent.
	 *
	 * @param Swift_Events_SendEvent $evt
	 */
	public function beforeSendPerformed(Swift_Events_SendEvent $evt)
	{
		$message = $evt->getMessage();
		$this->_restoreMessage($message);
		$to = array_keys($message->getTo());
		$address = array_shift($to);
		if ($replacements = $this->getReplacementsFor($address)) {
			$body = $message->getBody();
			$search = array_keys($replacements);
			$replace = array_values($replacements);
			$bodyReplaced = str_replace(
				$search, $replace, $body
			);
			if ($body != $bodyReplaced) {
				$this->_originalBody = $body;
				$message->setBody($bodyReplaced);
			}

			foreach ($message->getHeaders()->getAll() as $header) {
				$body = $header->getFieldBodyModel();
				$count = 0;
				if (is_array($body)) {
					$bodyReplaced = array();
					foreach ($body as $key => $value) {
						$count1 = 0;
						$count2 = 0;
						$key = is_string($key) ? str_replace($search, $replace, $key, $count1) : $key;
						$value = is_string($value) ? str_replace($search, $replace, $value, $count2) : $value;
						$bodyReplaced[$key] = $value;

						if (!$count && ($count1 || $count2)) {
							$count = 1;
						}
					}
				} else {
					$bodyReplaced = str_replace($search, $replace, $body, $count);
				}

				if ($count) {
					$this->_originalHeaders[$header->getFieldName()] = $body;
					$header->setFieldBodyModel($bodyReplaced);
				}
			}

			$children = (array)$message->getChildren();
			foreach ($children as $child) {
				list($type,) = sscanf($child->getContentType(), '%[^/]/%s');
				if ('text' == $type) {
					$body = $child->getBody();
					$bodyReplaced = str_replace(
						$search, $replace, $body
					);
					if ($body != $bodyReplaced) {
						$child->setBody($bodyReplaced);
						$this->_originalChildBodies[$child->getId()] = $body;
					}
				}
			}
			$this->_lastMessage = $message;
		}
	}

	/**
	 * Find a map of replacements for the address.
	 *
	 * If this plugin was provided with a delegate instance of
	 * {@link Swift_Plugins_Decorator_Replacements} then the call will be
	 * delegated to it.  Otherwise, it will attempt to find the replacements
	 * from the array provided in the constructor.
	 *
	 * If no replacements can be found, an empty value (NULL) is returned.
	 *
	 * @param string $address
	 *
	 * @return array
	 */
	public function getReplacementsFor($address)
	{
		if ($this->_replacements instanceof Swift_Plugins_Decorator_Replacements) {
			return $this->_replacements->getReplacementsFor($address);
		} else {
			return isset($this->_replacements[$address])
				? $this->_replacements[$address]
				: null;
		}
	}

	/**
	 * Invoked immediately after the Message is sent.
	 *
	 * @param Swift_Events_SendEvent $evt
	 */
	public function sendPerformed(Swift_Events_SendEvent $evt)
	{
		$this->_restoreMessage($evt->getMessage());
	}

	// -- Private methods

	/** Restore a changed message back to its original state */
	private function _restoreMessage(Swift_Mime_Message $message)
	{
		if ($this->_lastMessage === $message) {
			if (isset($this->_originalBody)) {
				$message->setBody($this->_originalBody);
				$this->_originalBody = null;
			}
			if (!empty($this->_originalHeaders)) {
				foreach ($message->getHeaders()->getAll() as $header) {
					if (array_key_exists($header->getFieldName(), $this->_originalHeaders)) {
						$header->setFieldBodyModel($this->_originalHeaders[$header->getFieldName()]);
					}
				}
				$this->_originalHeaders = array();
			}
			if (!empty($this->_originalChildBodies)) {
				$children = (array)$message->getChildren();
				foreach ($children as $child) {
					$id = $child->getId();
					if (array_key_exists($id, $this->_originalChildBodies)) {
						$child->setBody($this->_originalChildBodies[$id]);
					}
				}
				$this->_originalChildBodies = array();
			}
			$this->_lastMessage = null;
		}
	}
}
